package fileVarint

import (
	"encoding/binary"
	"os"
	"sync"
)

const (
	// Number of bytes to encode 0 in uvarint format
	headerSize = 4

// Bytes before left margin are not used. Zero index means element does not exist in queue, useful while reading slice from index
// leftMarginIndex = 1
)

// var (
// errEmptyQueue       = &queueError{"Empty queue"}
// errInvalidIndex     = &queueError{"Index must be greater than zero. Invalid index."}
// errIndexOutOfBounds = &queueError{"Index out of range"}
// )

// File is a non-thread safe queue type of fifo based on bytes array.
// For every push operation index of entry is returned. It can be used to read the entry later
type File struct {
	sync.Mutex
	// full         bool
	array []byte
	file  *os.File
	// capacity     int
	// maxCapacity  int
	// head         int
	tail     int
	FileName string
	// count        int
	// rightMargin  int
	headerBuffer []byte
	close        bool
}

// type queueError struct {
// message string
// }

// getUvarintSize returns the number of bytes to encode x in uvarint format
// func getUvarintSize(x uint32) int {
// if x < 128 {
// return 1
// } else if x < 16384 {
// return 2
// } else if x < 2097152 {
// return 3
// } else if x < 268435456 {
// return 4
// } else {
// return 5
// }
// }

// NewBytesQueue initialize new bytes queue.
// capacity is used in bytes array allocation
// When verbose flag is set then information about memory allocation are printed

func New() (f *File) {
	return &File{
		tail:         0,
		headerBuffer: make([]byte, headerSize),
		close:        false,
	}
}

func (q *File) Open(fileName string) (err error) {
	q.Lock()
	defer q.Unlock()
	if q.close {
		return
	}
	q.file, err = os.OpenFile(fileName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666)
	if err != nil {
		return
	}
	q.FileName = fileName
	return
}

func (q *File) Close() {
	q.Lock()
	defer q.Unlock()
	q.close = true
	if q.file != nil {
		q.file.Close()
		q.file = nil
        q.headerBuffer = nil
		q.FileName = ""
	}
}

func (q *File) Remove() {
	q.Lock()
	defer q.Unlock()
	q.close = true
	if q.file != nil {
		q.file.Close()
		q.file = nil
		q.headerBuffer = nil
		if len(q.FileName) > 0 {
			os.Remove(q.FileName)
		}
		q.FileName = ""
	}
}

// Returns index for pushed data or error if maximum size queue limit is reached.
func (q *File) Write(data []byte) (index int, err error) {
	q.Lock()
	defer q.Unlock()
	if q.file == nil {
		return
	}
	dataLen := len(data)
	// headerEntrySize := getUvarintSize(uint32(dataLen))
	index = q.tail
	q.file.Seek(0, os.SEEK_END)
	err = q.write(data, dataLen)
	return
}

func (q *File) write(data []byte, len int) (err error) {
	// headerEntrySize := binary.PutUvarint(q.headerBuffer, uint64(len))
    headerBuffer := make([]byte,headerSize)
	binary.LittleEndian.PutUint32(headerBuffer, uint32(len))
	err = q.copy(headerBuffer, headerSize)
	if err == nil {
		q.copy(data, len)
	}
	return
}

func (q *File) copy(data []byte, len int) error {
	f := q.file
	n, err := f.Write(data[:len])
	q.tail += n
	return err
}

// Get reads entry from index
func (q *File) Read(index int) ([]byte, error) {
	q.Lock()
	defer q.Unlock()
	if q.file == nil {
		return nil, nil
	}
	data, err := q.read(index)
	q.file.Seek(0, os.SEEK_END)
	return data, err
}

// binary.LittleEndian.Uint16
// binary.LittleEndian.PutUint16(blob[timestampSizeInBytes+hashSizeInBytes:], uint16(keyLength))
// peek returns the data from index and the number of bytes to encode the length of the data in uvarint format
func (q *File) read(index int) (out []byte, err error) {
    func(){
        recover()
    }()
	f := q.file
	f.Seek(int64(index), os.SEEK_SET)
	n := 0
	n, err = f.Read(q.headerBuffer[:headerSize])
	if n != headerSize {
		return
	}
	length := binary.LittleEndian.Uint32(q.headerBuffer[:headerSize])
	if length == 0 {
		return
	}
	// blockSize, n := binary.Uvarint(q.array[index:])
	out = make([]byte, length)
	n, err = f.Read(out)
	if n != int(length) {
		out = nil
	}
	return
}

// Len returns number of entries kept in queue
func (q *File) Len() int {
	q.Lock()
	defer q.Unlock()
	return q.tail
}

// Error returns error message
// func (e *queueError) Error() string {
// return e.message
// }
